/*
 * Copyright (C) 2013 Manuel Peinado
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.smartandroid.sa.fading;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListView;

@SuppressWarnings("unchecked")
public abstract class FadingActionBarHelperBase {
	protected static final String TAG = "FadingActionBarHelper";
	private Drawable mActionBarBackgroundDrawable;
	private FrameLayout mHeaderContainer;
	private int mActionBarBackgroundResId;
	private int mHeaderLayoutResId;
	private View mHeaderView;
	private int mHeaderOverlayLayoutResId;
	private View mHeaderOverlayView;
	private int mContentLayoutResId;
	private View mContentView;
	private LayoutInflater mInflater;
	private boolean mLightActionBar;
	private boolean mUseParallax = true;
	private int mLastDampedScroll;
	private int mLastHeaderHeight = -1;
	private boolean mFirstGlobalLayoutPerformed;
	private FrameLayout mMarginView;
	private View mListViewBackgroundView;

	public final <T extends FadingActionBarHelperBase> T actionBarBackground(
			int drawableResId) {
		mActionBarBackgroundResId = drawableResId;
		return (T) this;
	}

	public final <T extends FadingActionBarHelperBase> T actionBarBackground(
			Drawable drawable) {
		mActionBarBackgroundDrawable = drawable;
		return (T) this;
	}

	public final <T extends FadingActionBarHelperBase> T headerLayout(
			int layoutResId) {
		mHeaderLayoutResId = layoutResId;
		return (T) this;
	}

	public final <T extends FadingActionBarHelperBase> T headerView(View view) {
		mHeaderView = view;
		return (T) this;
	}

	public final <T extends FadingActionBarHelperBase> T headerOverlayLayout(
			int layoutResId) {
		mHeaderOverlayLayoutResId = layoutResId;
		return (T) this;
	}

	public final <T extends FadingActionBarHelperBase> T headerOverlayView(
			View view) {
		mHeaderOverlayView = view;
		return (T) this;
	}

	public final <T extends FadingActionBarHelperBase> T contentLayout(
			int layoutResId) {
		mContentLayoutResId = layoutResId;
		return (T) this;
	}

	public final <T extends FadingActionBarHelperBase> T contentView(View view) {
		mContentView = view;
		return (T) this;
	}

	public final <T extends FadingActionBarHelperBase> T lightActionBar(
			boolean value) {
		mLightActionBar = value;
		return (T) this;
	}

	public final <T extends FadingActionBarHelperBase> T parallax(boolean value) {
		mUseParallax = value;
		return (T) this;
	}

	private Context c;

	public final View createView(Context context) {
		this.c = context;
		return createView(LayoutInflater.from(context));
	}

	public final View createView(LayoutInflater inflater) {
		//
		// Prepare everything

		mInflater = inflater;
		if (mContentView == null) {
			mContentView = inflater.inflate(mContentLayoutResId, null);
		}
		if (mHeaderView == null) {
			mHeaderView = inflater.inflate(mHeaderLayoutResId, null, false);
		}

		//
		// See if we are in a ListView, WebView or ScrollView scenario

		ListView listView = (ListView) mContentView
				.findViewById(android.R.id.list);
		View root;
		if (listView != null) {
			root = createListView(listView);
		} else if (mContentView instanceof ObservableWebViewWithHeader) {
			root = createWebView();
		} else {
			root = createScrollView();
		}

		if (mHeaderOverlayView == null && mHeaderOverlayLayoutResId != 0) {
			mHeaderOverlayView = inflater.inflate(mHeaderOverlayLayoutResId,
					mMarginView, false);
		}
		if (mHeaderOverlayView != null) {
			mMarginView.addView(mHeaderOverlayView);
		}

		// Use measured height here as an estimate of the header height, later
		// on after the layout is complete
		// we'll use the actual height
		int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
				LayoutParams.MATCH_PARENT, MeasureSpec.EXACTLY);
		int heightMeasureSpec = MeasureSpec.makeMeasureSpec(
				LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY);
		mHeaderView.measure(widthMeasureSpec, heightMeasureSpec);
		updateHeaderHeight(mHeaderView.getMeasuredHeight());

		root.getViewTreeObserver().addOnGlobalLayoutListener(
				new OnGlobalLayoutListener() {
					@Override
					public void onGlobalLayout() {
						int headerHeight = mHeaderContainer.getHeight();
						if (!mFirstGlobalLayoutPerformed && headerHeight != 0) {
							updateHeaderHeight(headerHeight);
							mFirstGlobalLayoutPerformed = true;
						}
					}
				});
		return root;
	}

	public void initActionBar(Activity activity) {
		this.c = activity;
		if (mActionBarBackgroundDrawable == null) {
			mActionBarBackgroundDrawable = activity.getResources().getDrawable(
					mActionBarBackgroundResId);
		}
		setActionBarBackgroundDrawable(mActionBarBackgroundDrawable);
		if (Build.VERSION.SDK_INT <= 15) {
			// if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
			mActionBarBackgroundDrawable.setCallback(mDrawableCallback);
		}
		mActionBarBackgroundDrawable.setAlpha(0);
	}

	protected abstract int getActionBarHeight();

	protected abstract boolean isActionBarNull();

	protected abstract void setActionBarBackgroundDrawable(Drawable drawable);

	protected <T> T getActionBarWithReflection(Activity activity,
			String methodName) {
		try {
			Method method = activity.getClass().getMethod(methodName);
			return (T) method.invoke(activity);
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (ClassCastException e) {
			e.printStackTrace();
		}
		return null;
	}

	private Drawable.Callback mDrawableCallback = new Drawable.Callback() {
		@Override
		public void invalidateDrawable(Drawable who) {
			setActionBarBackgroundDrawable(who);
		}

		@Override
		public void scheduleDrawable(Drawable who, Runnable what, long when) {
		}

		@Override
		public void unscheduleDrawable(Drawable who, Runnable what) {
		}
	};

	private View createWebView() {
		ViewGroup webViewContainer = (ViewGroup) mInflater.inflate(
				c.getResources().getIdentifier("fab__webview_container",
						"layout", c.getPackageName()), null);
		// R.layout.fab__webview_container, null);

		ObservableWebViewWithHeader webView = (ObservableWebViewWithHeader) mContentView;
		webView.setOnScrollChangedCallback(mOnScrollChangedListener);

		webViewContainer.addView(webView);

		mHeaderContainer = (FrameLayout) webViewContainer.findViewById(c
				.getResources().getIdentifier("fab__header_container", "id",
						c.getPackageName()));
		// .findViewById(R.id.fab__header_container);
		initializeGradient(mHeaderContainer);
		mHeaderContainer.addView(mHeaderView, 0);

		mMarginView = new FrameLayout(webView.getContext());
		mMarginView.setBackgroundColor(Color.TRANSPARENT);
		mMarginView.setLayoutParams(new ViewGroup.LayoutParams(
				ViewGroup.LayoutParams.MATCH_PARENT,
				ViewGroup.LayoutParams.WRAP_CONTENT));
		webView.addView(mMarginView);

		return webViewContainer;
	}

	private View createScrollView() {
		ViewGroup scrollViewContainer = (ViewGroup) mInflater.inflate(
				c.getResources().getIdentifier("fab__scrollview_container",
						"layout", c.getPackageName()), null);
		// R.layout.fab__scrollview_container, null);

		ObservableScrollView scrollView = (ObservableScrollView) scrollViewContainer
				.findViewById(c.getResources().getIdentifier(
						"fab__scroll_view", "id", c.getPackageName()));
		// .findViewById(R.id.fab__scroll_view);
		scrollView.setOnScrollChangedCallback(mOnScrollChangedListener);

		ViewGroup contentContainer = (ViewGroup) scrollViewContainer
				.findViewById(c.getResources().getIdentifier("fab__container",
						"id", c.getPackageName()));
		// .findViewById(R.id.fab__container);
		LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
				LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
		mContentView.setLayoutParams(layoutParams);
		contentContainer.addView(mContentView);
		mHeaderContainer = (FrameLayout) scrollViewContainer.findViewById(c
				.getResources().getIdentifier("fab__header_container", "id",
						c.getPackageName()));
		// .findViewById(R.id.fab__header_container);
		initializeGradient(mHeaderContainer);
		mHeaderContainer.addView(mHeaderView, 0);
		mMarginView = (FrameLayout) contentContainer.findViewById(c
				.getResources().getIdentifier("fab__content_top_margin", "id",
						c.getPackageName()));
		// .findViewById(R.id.fab__content_top_margin);

		return scrollViewContainer;
	}

	private OnScrollChangedCallback mOnScrollChangedListener = new OnScrollChangedCallback() {
		public void onScroll(int l, int t) {
			onNewScroll(t);
		}
	};

	private View createListView(ListView listView) {
		ViewGroup contentContainer = (ViewGroup) mInflater.inflate(
				c.getResources().getIdentifier("fab__listview_container",
						"layout", c.getPackageName()), null);
		// R.layout.fab__listview_container, null);
		contentContainer.addView(mContentView);

		mHeaderContainer = (FrameLayout) contentContainer.findViewById(c
				.getResources().getIdentifier("fab__header_container", "id",
						c.getPackageName()));
		// .findViewById(R.id.fab__header_container);
		initializeGradient(mHeaderContainer);
		mHeaderContainer.addView(mHeaderView, 0);

		mMarginView = new FrameLayout(listView.getContext());
		mMarginView.setLayoutParams(new AbsListView.LayoutParams(
				LayoutParams.MATCH_PARENT, 0));
		listView.addHeaderView(mMarginView, null, false);

		// Make the background as high as the screen so that it fills regardless
		// of the amount of scroll.
		mListViewBackgroundView = contentContainer.findViewById(c
				.getResources().getIdentifier("fab__listview_background", "id",
						c.getPackageName()));
		// .findViewById(R.id.fab__listview_background);
		FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mListViewBackgroundView
				.getLayoutParams();
		params.height = Utils.getDisplayHeight(listView.getContext());
		mListViewBackgroundView.setLayoutParams(params);

		listView.setOnScrollListener(mOnScrollListener);
		return contentContainer;
	}

	private OnScrollListener mOnScrollListener = new OnScrollListener() {
		@Override
		public void onScroll(AbsListView view, int firstVisibleItem,
				int visibleItemCount, int totalItemCount) {
			View topChild = view.getChildAt(0);
			if (topChild == null) {
				onNewScroll(0);
			} else if (topChild != mMarginView) {
				onNewScroll(mHeaderContainer.getHeight());
			} else {
				onNewScroll(-topChild.getTop());
			}
		}

		@Override
		public void onScrollStateChanged(AbsListView view, int scrollState) {
		}
	};
	private int mLastScrollPosition;

	private void onNewScroll(int scrollPosition) {
		if (isActionBarNull()) {
			return;
		}

		int currentHeaderHeight = mHeaderContainer.getHeight();
		if (currentHeaderHeight != mLastHeaderHeight) {
			updateHeaderHeight(currentHeaderHeight);
		}

		int headerHeight = currentHeaderHeight - getActionBarHeight();
		float ratio = (float) Math.min(Math.max(scrollPosition, 0),
				headerHeight) / headerHeight;
		int newAlpha = (int) (ratio * 255);
		mActionBarBackgroundDrawable.setAlpha(newAlpha);

		addParallaxEffect(scrollPosition);
	}

	private void addParallaxEffect(int scrollPosition) {
		float damping = mUseParallax ? 0.5f : 1.0f;
		int dampedScroll = (int) (scrollPosition * damping);
		int offset = mLastDampedScroll - dampedScroll;
		mHeaderContainer.offsetTopAndBottom(offset);

		if (mListViewBackgroundView != null) {
			offset = mLastScrollPosition - scrollPosition;
			mListViewBackgroundView.offsetTopAndBottom(offset);
		}

		if (mFirstGlobalLayoutPerformed) {
			mLastScrollPosition = scrollPosition;
			mLastDampedScroll = dampedScroll;
		}
	}

	private void updateHeaderHeight(int headerHeight) {
		ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) mMarginView
				.getLayoutParams();
		params.height = headerHeight;
		mMarginView.setLayoutParams(params);
		if (mListViewBackgroundView != null) {
			FrameLayout.LayoutParams params2 = (FrameLayout.LayoutParams) mListViewBackgroundView
					.getLayoutParams();
			params2.topMargin = headerHeight;
			mListViewBackgroundView.setLayoutParams(params2);
		}
		mLastHeaderHeight = headerHeight;
	}

	private void initializeGradient(ViewGroup headerContainer) {
		View gradientView = headerContainer.findViewById(c.getResources()
				.getIdentifier("fab__gradient", "id", c.getPackageName()));
		// View gradientView = headerContainer.findViewById(R.id.fab__gradient);
		int gradient = c.getResources().getIdentifier("fab__gradient",
				"drawable", c.getPackageName());
		// int gradient = R.drawable.fab__gradient;
		if (mLightActionBar) {
			gradient = c.getResources().getIdentifier("fab__gradient_light",
					"drawable", c.getPackageName());
			// gradient = R.drawable.fab__gradient_light;
		}
		gradientView.setBackgroundResource(gradient);
	}
}
